using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Needle.Bus.Engine.Utils;
using System.Threading;
using log4net;
using Needle.Utils.Collections.Threadsafe;
using System.ServiceModel;
using Needle.Utils.Collections;

namespace Needle.Bus.Engine.Schedulers
{

    public abstract class Worker<T> : EngineComponent, IExtension<T>, IQueue<IWorkItem>
            where T : class, IScheduler<T>, IExtensibleObject<T>
    {

        private readonly Thread _thread;
        private readonly WorkQueue _workQueue;
        private int _running;
        public bool Running
        {
            get { return _running==1; }
        }
        
        public IQueue<IWorkItem> Work
        {
            get { return this; }
        }
        
        #region constructors

        public Worker(string id)
            : base(new NamedMutex(id), id)
        {
            _workQueue = new WorkQueue();
            _thread = new Thread(ThreadStart);
            _thread.Name = this.Id;
        }

        #endregion

        #region State transitions
        
        Bus _bus;

        protected override void OnOpen(TimeSpan timeout)
        {
            lock (ThisLock)
            {
                Interlocked.CompareExchange(ref _running, 1, 0);
                _bus = new Bus(Bus.Context.Engine);
                _thread.Start();
            }
        }

        protected override void OnAbort()
        {
            /* do nothing */
        }

        protected override void OnClose(TimeSpan timeout)
        {
            bool shouldJoin = false;
            Interlocked.CompareExchange(ref _running, 0, 1);
            lock (ThisLock)
            {
                shouldJoin = _thread != null
                    && _thread != Thread.CurrentThread
                    && _thread.ThreadState != ThreadState.Unstarted;
            }
            try
            {
                if (shouldJoin)
                    _thread.Join(timeout);
            }
            catch (ThreadInterruptedException)
            { }
        }

        #endregion

        #region Worker implementation

        private void ThreadStart()
        {
            using (var ctx = _bus.SetCurrent())
            {
                Run();
            }
        }

        protected abstract void Run();

        public abstract void Attach(T owner);

        public abstract void Detach(T owner);

        #endregion

        #region IQueue<WorkItem>

        void IQueue<IWorkItem>.Enqueue(IWorkItem item)
        {
            ThrowIfDisposed();
            _workQueue.Enqueue(item);
        }

        IWorkItem IQueue<IWorkItem>.Dequeue()
        {
            ThrowIfDisposed();
            return _workQueue.Dequeue();
        }

        int IQueue<IWorkItem>.Count
        {
            /* never throw here. */
            get { return _workQueue.Count; }
        }

        bool IQueue<IWorkItem>.TryDequeue(out IWorkItem item)
        {
            /* never throw here. */
            switch (State)
            {   /* there is a race here but we don't care, it is benign. */
                case CommunicationState.Opened:
                case CommunicationState.Created:
                case CommunicationState.Opening:
                    return _workQueue.TryDequeue(out item);

                default:
                    item = null;
                    return false;
            }
        }
            
        bool IQueue<IWorkItem>.TryEnqueue(IWorkItem item)
        {
            /* never throw here. */
            switch (State)
            {   /* there is a race here but we don't care, it is benign. */
                case CommunicationState.Opened:
                case CommunicationState.Created:
                case CommunicationState.Opening:
                    return _workQueue.TryEnqueue(item);

                default:
                    return false;
            }
        }

        #endregion

        /// <summary>
        /// Drains all work items from the work queue into the sink.
        /// </summary>
        internal void DrainWork(Action<IWorkItem> sink)
        {
            IWorkItem work = null;
            while (_workQueue.TryDequeue(out work))
                sink(work); 
        }
    }
}
